﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ECSE6770.Models;
using ECSE6770.Payment;
using com.paypal.sdk.services;
using com.paypal.sdk.profiles;
using com.paypal.sdk.util;

namespace ECSE6770.Controllers
{
    [RequireHttps]
    [Authorize]
    public class CartController : BaseController
    {
        private const string DIRECT_PAYMENT_MODEL = "ECSE6770.Controllers.CartController.DirectPaymentModel";

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            ViewBag.Title = "Shopping Cart";
            base.OnActionExecuting(filterContext);
        }

        public ActionResult Index()
        {
            return View(Cart.Current);
        }

        public ActionResult Expired()
        {
            return View();
        }

        public ActionResult AddModule(int id)
        {
            if (ContextManager.CurrentUser.Interop.IsRegisteredFor(id)) //don't let a user register twice
                return View("AlreadyReg");
            else if (!ContextManager.CurrentUser.HasAllRequiredFields()) //when adding a module to the cart, we need to ensure that a user has filled out their profile
                return RedirectToAction("RegEditProfile", "Account", new { id = id });
            else
            {
                var module = Db.Modules.First(m => m.Id == id);
                Cart.Current.AddItem(module);
                return View("Added", module);
            }
        }

        public ActionResult AddVideo(int id)
        {
            if (ContextManager.CurrentUser.Interop.OwnsVideo(id))
                return View("AlreadyOwned");
            else
            {
                var video = Db.Videos.First(v => v.Id == id);
                Cart.Current.AddItem(video);
                return View("Added", video);
            }
        }

        public ActionResult RemoveItem(int id)
        {
            Cart.Current.RemoveItem(id);
            return ToIndex();
        }

        public ActionResult Checkout()
        {
            if (Cart.Current.Count == 0)
                return ToIndex();
            else
                return View();
        }

        public ActionResult CancelPayment()
        {
            Cart.Current.Clear();
            return View();
        }

        public ActionResult PaymentComplete()
        {
            return View();
        }

        public ActionResult DirectPayment()
        {
            var model = new DirectPaymentModel();

#if DEBUG
            model.FirstName = "John";
            model.LastName = "Doe";
            model.CardType = "Visa";
            model.CardNumber = Util.GenerateCreditCardNumber(model.CardType);
            model.CardVerificationNumber = "962";
            model.ExpirationMonth = "01";
            model.ExpirationYear = "2012";
            model.BillingAddress1 = "123 Fake St";
            model.City = "Omaha";
            model.State = "NE";
            model.ZipCode = "68104";
#endif

            return View(model);
        }

        [HttpPost]
        public ActionResult DirectPayment(DirectPaymentModel model)
        {
            CheckExpiredSession();
            if (ModelState.IsValid)
            {
                StorePaymentInfo(model);
                return RedirectToAction("ConfirmDirectPayment");
            }
            else
                return View(model);
        }

        public ActionResult ConfirmDirectPayment()
        {
            CheckExpiredSession();
            return View(RetrievePaymentInfo());
        }

        [HttpPost]
        public ActionResult DoDirectPayment()
        {
            CheckExpiredSession();

            SetupPaymentRequest(Cart.Current);
            if (MakePayment(RetrievePaymentInfo(), Cart.Current))
            {
                //this is a total hack to get around some crazy Entity Framework exceptions
                return RedirectToAction("Finalize");
            }
            else
            {
                //todo - fail gracefully and log
                throw new Exception("payment failed");
            }
        }

        public ActionResult Finalize()
        {
            Cart.Current.ApplyTransaction();
            return RedirectToAction("PaymentComplete");
        }

        private void StorePaymentInfo(DirectPaymentModel model)
        {
            Session[DIRECT_PAYMENT_MODEL] = model;
        }

        private DirectPaymentModel RetrievePaymentInfo()
        {
            return Session[DIRECT_PAYMENT_MODEL] as DirectPaymentModel;
        }

        private void CheckExpiredSession()
        {
            if (Cart.Current.Count == 0)
                Response.RedirectToRoute("Expired");
        }

        private void SetupPaymentRequest(Cart cart)
        {
            // See the DigitalGoodsExample project for the source of this, and a working example
            NVPCallerServices caller = new NVPCallerServices();
            IAPIProfile profile = ProfileFactory.createSignatureAPIProfile();

            // Set up your API credentials, PayPal end point, API operation and version.profile.
            profile.APIUsername = ECSE6770.Payment.Constants.API_USERNAME;
            profile.APIPassword = ECSE6770.Payment.Constants.API_PASSWORD;
            profile.APISignature = ECSE6770.Payment.Constants.API_SIGNATURE;
            profile.Environment = ECSE6770.Payment.Constants.ENVIRONMENT;

            caller.APIProfile = profile;
            NVPCodec encoder = new NVPCodec();
            encoder["VERSION"] = PaymentSettings.PayPalVersion;
            encoder["METHOD"] = "SetExpressCheckout";

            // Add request-specific fields to the request.

            //encoder["RETURNURL"] = "return.aspx"; - Not sure if this is needed only for direct payment?
            encoder["CANCELURL"] = Url.Action("CancelPayment");
            encoder["PAYMENTREQUEST_0_CURRENCYCODE"] = PaymentSettings.PaymentCurrency;
            encoder["PAYMENTREQUEST_0_PAYMENTACTION"] = "SALE";
            
            /* Note: this doesn't take taxes into account, so that's a to do. */
            encoder["PAYMENTREQUEST_0_AMT"] = cart.TotalCost.ToString();
            encoder["PAYMENTREQUEST_0_ITEMAMT"] = cart.TotalCost.ToString();
            encoder["PAYMENTREQUEST_0_TAXAMT"] = "0";
            encoder["PAYMENTREQUEST_0_DESC"] = PaymentSettings.PaymentRequestDescription;

            //add the items to the encoder
            cart.WriteItems(encoder);

            //make the payment request
            string pStrrequestforNvp = encoder.Encode();
            string pStresponsenvp = caller.Call(pStrrequestforNvp);
            NVPCodec decoder = new NVPCodec();
            decoder.Decode(pStresponsenvp);
            //note: the decoder object should be interrogated to ensure success, need to check the Paypal docs for this.
        }

        private bool MakePayment(DirectPaymentModel model, Cart cart)
        {
            NVPCallerServices caller = new NVPCallerServices();
            IAPIProfile profile = ProfileFactory.createSignatureAPIProfile();

            // Set up your API credentials, PayPal end point, API operation and version.profile.
            profile.APIUsername = ECSE6770.Payment.Constants.API_USERNAME;
            profile.APIPassword = ECSE6770.Payment.Constants.API_PASSWORD;
            profile.APISignature = ECSE6770.Payment.Constants.API_SIGNATURE;
            profile.Environment = ECSE6770.Payment.Constants.ENVIRONMENT;
            caller.APIProfile = profile;

            NVPCodec encoder = new NVPCodec();

            encoder["VERSION"] = PaymentSettings.PayPalVersion;
            encoder["METHOD"] = "DoDirectPayment";
            encoder["PAYMENTACTION"] = "SALE";
            encoder["AMT"] = cart.TotalCost.ToString();
            encoder["CREDITCARDTYPE"] = model.CardType;
            encoder["ACCT"] = model.CardNumber;
            encoder["EXPDATE"] = model.ExpirationMonth + model.ExpirationYear;
            encoder["CVV2"] = model.CardVerificationNumber;
            encoder["FIRSTNAME"] = model.FirstName;
            encoder["LASTNAME"] = model.LastName;
            string address = String.IsNullOrWhiteSpace(model.BillingAddress2) ? model.BillingAddress1 : String.Concat(model.BillingAddress1, Environment.NewLine, model.BillingAddress2);
            encoder["STREET"] = address;
            encoder["CITY"] = model.City;
            encoder["STATE"] = model.State;
            encoder["ZIP"] = model.ZipCode;
            encoder["COUNTRYCODE"] = CountryManager.IsoCountryCode(model.Country);
            encoder["CURRENCYCODE"] = PaymentSettings.PaymentCurrency;

            string pStrrequestforNvp = encoder.Encode();
            string pStresponsenvp = caller.Call(pStrrequestforNvp);

            NVPCodec decoder = new NVPCodec();
            decoder.Decode(pStresponsenvp);

            //log this transaction
            OrderHistoryManager.LogOrder(Cart.Current, decoder, pStresponsenvp);

            //check if this worked
            TempData["PaymentResult"] = decoder;
            string strAck = decoder["ACK"];
            return (strAck != null && (strAck == "Success" || strAck == "SuccessWithWarning")) ;
        }
    }
}
